#include "stdafx.h"
#include "popmft.h"
#include "POP.h"
#include "POP2.h"
#include "POP3.h"

#include "Array.h"
#include <wx/grid.h>

#include "Process.h"

IMPLEMENT_APP(popmftApp)

struct TrainerFrame : public wxFrame
{
	ProcessManager m_proc_manager;
	wxGrid* m_grid;
	wxChoice* m_process_list;
	wxPanel* p_main;
	wxTimer* m_timer;
	u32 m_editing_grid;

	TrainerFrame(wxWindow* parent) : wxFrame(NULL, wxID_ANY, "ProcessList")
	{
		m_editing_grid = 0;
		m_type = 0;

		p_main = new wxPanel(this);
		m_grid = new wxGrid(this, wxID_ANY);

		wxButton* btn_search = new wxButton(p_main, wxID_ANY, "Search");
		wxButton* btn_filter = new wxButton(p_main, wxID_ANY, "Filter");
		m_process_list = new wxChoice(p_main, wxID_ANY);
		wxButton* btn_ref_proc = new wxButton(p_main, wxID_ANY, "Refresh");

		wxButton* btn_ref_list = new wxButton(p_main, wxID_ANY, "Update values");

		wxBoxSizer& s_p_data = *new wxBoxSizer(wxHORIZONTAL);
		s_p_data.Add(btn_search, wxSizerFlags().Border());
		s_p_data.Add(btn_filter, wxSizerFlags().Border());
		s_p_data.Add(m_process_list, wxSizerFlags().Center().Expand().Border());
		s_p_data.Add(btn_ref_proc, wxSizerFlags().Border());

		wxBoxSizer& s_p_main = *new wxBoxSizer(wxVERTICAL);
		s_p_main.Add(&s_p_data, wxSizerFlags().Center());
		s_p_main.Add(btn_ref_list);
		p_main->SetSizerAndFit(&s_p_main);

		wxBoxSizer& s_main = *new wxBoxSizer(wxVERTICAL);
		s_main.Add(p_main, wxSizerFlags().Expand());
		s_main.Add(m_grid);
		SetSizerAndFit(&s_main);

		m_timer = new wxTimer(this);

		Connect(m_process_list->GetId(), wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler(TrainerFrame::OnLoad));
		Connect(btn_ref_proc->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(TrainerFrame::OnRefresh));
		Connect(btn_search->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(TrainerFrame::OnSearch));
		Connect(btn_filter->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(TrainerFrame::OnFilter));
		Connect(btn_ref_list->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(TrainerFrame::OnUpdate));
		
		Connect(wxEVT_SIZE, wxSizeEventHandler(TrainerFrame::OnResize));
		Connect(m_grid->GetId(), wxEVT_GRID_CELL_CHANGE,  wxGridEventHandler(TrainerFrame::OnCellChange));
		Connect(m_timer->GetId(), wxEVT_TIMER, wxTimerEventHandler(TrainerFrame::OnTimer));
		wxGetApp().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(TrainerFrame::OnKeyDown), (wxObject*)0, this);
		
		m_grid->CreateGrid(0, 3);
		m_grid->DisableDragCell();

		m_grid->SetColLabelValue(0, "Address");
		m_grid->SetColLabelValue(1, "Value");
		m_grid->SetColLabelValue(2, "Size");

		SetSize(800, 500);

		OnRefresh(wxCommandEvent());
		OnTimer(wxTimerEvent());
	}

	void OnKeyDown(wxKeyEvent& event)
	{
		if(event.GetKeyCode() == WXK_DELETE)
		{
			wxArrayInt raws = m_grid->GetSelectedRows();

			for(u32 i=0; i<raws.GetCount(); ++i)
			{
				m_args_list.RemoveAt(raws[i] - i);
				m_grid->DeleteRows(raws[i] - i);
			}
		}

		event.Skip();
	}

	void OnUpdate(wxCommandEvent&)
	{
		UpdateValues();
	}

	void OnTimer(wxTimerEvent&)
	{
		//if(UpdateValues()) m_timer->Start(1000, true);
	}

	bool UpdateValues()
	{
		for(u32 i=0; i<m_args_list.GetCount(); ++i)
		{
			if(!m_proc_manager.Read(m_args_list[i]))
			{
				wxMessageBox("Error");
				return false;
			}

			m_grid->SetCellValue(wxString::Format(m_args_list[i].type.fmt, m_proc_manager.re(m_args_list[i].value, m_args_list[i].size)), i, 1);
		}

		return true;
	}

	void OnCellChange(wxGridEvent& event)
	{
		if(event.GetCol() != 1)	return;

		PAddr& a = m_args_list[event.GetRow()];
		const wxString& value = m_grid->GetCellValue(event.GetRow(), 1);

		sscanf(value, a.type.fmt, &a.value);

		a.value = m_proc_manager.re(a.value, a.size);

		if(!m_proc_manager.Write(a))
		{
			wxMessageBox("Error");
		}
	}

	void OnLoad(wxCommandEvent&)
	{
		if(m_process_list->GetSelection() < 0) return;

		if(!m_proc_manager.Open(m_proc_manager.GetProcess(m_process_list->GetSelection()).szExeFile))
		{
			wxMessageBox("process opening error");
			m_process_list->SetSelection(-1);
			return;
		}

		if(m_grid->GetRows()) m_grid->DeleteRows(0, m_grid->GetRows());
	}

	void OnRefresh(wxCommandEvent&)
	{
		m_proc_manager.Update();
		m_process_list->Clear();

		for(u32 i=0; i<m_proc_manager.GetProcessCount(); ++i)
		{
			const wxString& item = wxString::Format("[0x%08x] %s", 
				m_proc_manager.GetProcess(i).th32ProcessID,
				m_proc_manager.GetProcess(i).szExeFile);

			m_process_list->Append(item);
		}

		GetSizer()->RecalcSizes();
		OnResize(wxSizeEvent());
	}

	bool ShowGetValueDialog(wxString& res, u32& type, const wxString& name = "Select value")
	{
		wxDialog* dial = new wxDialog(this, wxID_ANY, name, wxDefaultPosition);
		wxTextCtrl* text_entry = new wxTextCtrl(dial, wxID_ANY, res);
		wxStaticBoxSizer* ss_types = new wxStaticBoxSizer(wxVERTICAL, dial, "Types");

		ArrayF<wxCheckBox> types_list;

		for(u32 i=0; i<m_types_count; ++i)
		{
			wxCheckBox* cb = new wxCheckBox(dial, wxID_ANY, m_types[i].name);
			types_list.Add(cb);
			ss_types->Add(cb, wxSizerFlags().Border());
			cb->SetValue((type & (1 << i)) > 0);
		}

		wxBoxSizer& s_data = *new wxBoxSizer(wxHORIZONTAL);
		s_data.Add(text_entry, wxSizerFlags().Border());
		s_data.AddSpacer(5);
		s_data.Add(ss_types, wxSizerFlags().Border());

		wxBoxSizer& s_btns = *new wxBoxSizer(wxHORIZONTAL);
		s_btns.Add(new wxButton(dial, wxID_OK));
		s_btns.AddSpacer(5);
		s_btns.Add(new wxButton(dial, wxID_CANCEL));
		
		wxBoxSizer& s_main = *new wxBoxSizer(wxVERTICAL);
		s_main.Add(&s_data, wxSizerFlags().DoubleBorder().Center());
		s_main.Add(&s_btns, wxSizerFlags().Border().Center());

		dial->SetSizerAndFit(&s_main);

_SHOW_:
		if(dial->ShowModal() == wxID_CANCEL) return false;

		res = text_entry->GetValue();
		type = 0;
	
		for(u32 i=0; i<types_list.GetCount(); ++i)
		{
			if(!types_list[i].GetValue()) continue;

			type |= 1 << i;
		}

		if(!type)
		{
			wxMessageBox("Please, select type.");
			goto _SHOW_;
		}

		if(res.IsEmpty())
		{
			wxMessageBox("Null value.");
			goto _SHOW_;
		}

		res = text_entry->GetValue();

		return true;
	}

	u32 m_type;
	wxString m_def_value;
	Array<PAddr> m_args_list;

	void OnSearch(wxCommandEvent&)
	{
		if(!ShowGetValueDialog(m_def_value, m_type)) return;

		m_args_list.Clear();
		if(m_grid->GetRows()) m_grid->DeleteRows(0, m_grid->GetRows());

		u64 value;
		sscanf(m_def_value, "%lld", &value);
		m_proc_manager.Search(m_args_list, &value, m_type);

		wxMessageBox(wxString::Format("%d", m_args_list.GetCount()));
		m_grid->AppendRows(m_args_list.GetCount());
		
		for(u32 i=0; i<m_args_list.GetCount(); ++i)
		{
			m_grid->SetReadOnly(i, 0);
			m_grid->SetCellValue(wxString::Format("0x%x", (u32)m_args_list[i].addr), i, 0);
			m_grid->SetCellValue(wxString::Format(m_args_list[i].type.fmt, value), i, 1);
			m_grid->SetCellValue(wxString::Format("%d", m_args_list[i].size), i, 2);
		}
	}

	void OnFilter(wxCommandEvent&)
	{
		if(!ShowGetValueDialog(m_def_value, m_type)) return;

		for(u32 i=0; i<m_args_list.GetCount(); ++i)
		{
			if(m_type & m_args_list[i].type_id)
			{
				if(!m_proc_manager.Read(m_args_list[i]))
				{
					wxMessageBox("Filtering error");
					return;
				}

				const wxString& cur_value = wxString::Format(m_args_list[i].type.fmt, m_proc_manager.re(m_args_list[i].value, m_args_list[i].type.size));

				if(cur_value.CmpNoCase(m_def_value) == 0)
				{
					m_grid->SetCellValue(cur_value, i, 1);
					m_grid->SetCellValue(wxString::Format("%d", m_args_list[i].type.size), i, 2);
					continue;
				}
			}

			m_args_list.RemoveAt(i);
			m_grid->DeleteRows(i);

			--i;
		}

		wxMessageBox(wxString::Format("%d", m_args_list.GetCount()));
	}

	void OnResize(wxSizeEvent&)
	{
		wxSize min = p_main->GetSizer()->CalcMin();
		p_main->SetSize(GetClientSize().x, min.y);
		m_grid->SetSize(GetClientSize().x, GetClientSize().y - min.y);
		SetMinSize(min);
	}
};

struct GamesListEntry
{
	wxString name;
	FileTableGUI* save_editor_table;
	ProcessTableGUI* trainer_table;
};

GamesListEntry m_saves_list_table[] =
{
	{"[PSP] Prince of Persia: Revelations", new POP2(), new TrainerPOP2()},
	{"[PSP] Prince of Persia: Rival Swords", new POP3(), nullptr},
};

static const u32 m_saves_list_size = WXSIZEOF(m_saves_list_table);

class MainFrame : public wxFrame
{
	TableGUI* m_reader;
	wxPanel& p_main;
	wxString m_path;
	int m_id_refresh;

public:
	MainFrame()
		: wxFrame(NULL, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize,
			wxDEFAULT_FRAME_STYLE & ~(wxRESIZE_BORDER | wxMAXIMIZE_BOX))
		, p_main(*new wxPanel(this))
		, m_reader(nullptr)
		, m_path(wxEmptyString)
		, m_id_refresh(wxNewId())
	{
		wxMenuBar& menubar(*new wxMenuBar());
		wxMenu& menu_file(*new wxMenu());
		menubar.Append(&menu_file, "File");
		int id_select_file = menu_file.Append(wxID_ANY, "Select file...")->GetId();
		wxMenuItem* item_proc = menu_file.Append(wxID_ANY, "Select process...");
		menu_file.AppendSeparator();
		int id_exit = menu_file.Append(wxID_CANCEL, "Exit")->GetId();

		item_proc->Enable(ProcessManager::GetDebugPrivilege());

		SetMenuBar(&menubar);

		wxBoxSizer& s_main = *new wxBoxSizer(wxVERTICAL);
		s_main.Add(&p_main);
		SetSizerAndFit(&s_main);

		SetLabel(wxGetApp().GetAppName());

		Connect(id_exit,			wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainFrame::OnExit));
		Connect(id_select_file,		wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainFrame::OnSelectFile));
		Connect(item_proc->GetId(),	wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainFrame::OnSelectProcess));
		Connect(m_id_refresh,		wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainFrame::OnRefresh));
		Connect(wxID_OK,			wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainFrame::OnOk));
		Connect(wxEVT_SIZE, wxSizeEventHandler(MainFrame::OnResize));
	}

	void BuildGui(wxBoxSizer* sizer)
	{
		wxBoxSizer& s_main = *new wxBoxSizer(wxVERTICAL);

		s_main.Add(m_reader->GetBoxSizer(&p_main), wxSizerFlags().Expand().Border(wxALL, 5).Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));

		if(sizer)
		{
			s_main.Add(new wxStaticLine(&p_main), wxSizerFlags().Border(wxLEFT | wxRIGHT, 5).Expand());
			s_main.Add(sizer, wxSizerFlags().Expand());
		}

		p_main.SetSizerAndFit(&s_main);
	}

	void BuildGuiFile()
	{
		m_reader->Load();
		p_main.DestroyChildren();

		wxBoxSizer& s_main = *new wxBoxSizer(wxVERTICAL);

		s_main.Add(new wxButton(&p_main, wxID_OK, "Save"), wxSizerFlags().Border(wxALL, 5).Align(wxALIGN_RIGHT));

		BuildGui(&s_main);
	}

	void BuildGuiProcess()
	{
		m_reader->Load();
		p_main.DestroyChildren();

		wxBoxSizer& s_main = *new wxBoxSizer(wxHORIZONTAL);

		s_main.Add(new wxButton(&p_main, m_id_refresh, "Refresh"),	wxSizerFlags().Border(wxALL, 5));
		s_main.Add(new wxButton(&p_main, wxID_OK, "Apply"),			wxSizerFlags().Border(wxALL, 5));

		BuildGui(&s_main);
	}

	int SelectFile()
	{
		wxFileDialog ctrl(this, L"Select save", wxEmptyString, m_path, "*.*", wxFD_OPEN | wxFD_FILE_MUST_EXIST);

		if(ctrl.ShowModal() == wxID_CANCEL) return 2;

		m_path = ctrl.GetPath();

		FileTableGUI* reader = nullptr;

		for(u32 i=0; i<m_saves_list_size; ++i)
		{
			if( !m_saves_list_table[i].save_editor_table ||
				!m_saves_list_table[i].save_editor_table->Test(m_path) ) continue;

			reader = m_saves_list_table[i].save_editor_table;
			SetLabel(wxGetApp().GetAppName() + " - " + m_saves_list_table[i].name);
			break;
		}

		if(!reader)
		{
			wxMessageBox("Unsupported file type.");
			return 1;
		}

		if(m_reader) m_reader->Close();

		if(!reader->Open(m_path))
		{
			wxMessageBox("Opening error.");
			return 1;
		}

		m_reader = reader;
		return 0;
	}

	int SelectProcess()
	{
		ProcessManager proc_manager;

		wxDialog select_proc_dialog(this, wxID_ANY, "Select process", wxDefaultPosition);
		wxChoice& choice = *new wxChoice(&select_proc_dialog, wxID_ANY);

		for(u32 i=0; i<proc_manager.GetProcessCount(); ++i)
		{
			for(u32 l=0; l<m_saves_list_size; ++l)
			{
				if( !m_saves_list_table[l].trainer_table ||
					!m_saves_list_table[l].trainer_table->Test(proc_manager.GetProcess(i).szExeFile) ) continue;

				choice.Append(proc_manager.GetProcess(i).szExeFile);
				break;
			}
		}

		wxBoxSizer& s_buttons = *new wxBoxSizer(wxHORIZONTAL);
		s_buttons.Add(new wxButton(&select_proc_dialog, wxID_OK), wxSizerFlags().Border(wxALL, 5));
		s_buttons.Add(new wxButton(&select_proc_dialog, wxID_CANCEL), wxSizerFlags().Border(wxALL, 5));

		wxBoxSizer& s_main = *new wxBoxSizer(wxVERTICAL);
		s_main.Add(&choice, wxSizerFlags().Expand().Border(wxALL, 20).Align(wxALIGN_CENTER_VERTICAL));
		s_main.Add(new wxStaticLine(&select_proc_dialog), wxSizerFlags().Border(wxLEFT | wxRIGHT, 5).Expand());
		s_main.Add(&s_buttons, wxSizerFlags().Border(wxALL, 5).Expand().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_BOTTOM));

		select_proc_dialog.SetSizerAndFit(&s_main);

		select_proc_dialog.SetSize(400, -1);
		if(select_proc_dialog.ShowModal() == wxID_CANCEL) return 2;

		if(choice.GetSelection() < 0)
		{
			wxMessageBox("Process is not selected.");
			return 1;
		}
		
		u32 process_num = 0;
		bool finded = false;
		for(u32 i=0; i<proc_manager.GetProcessCount() && !finded; ++i)
		{
			for(u32 l=0; l<m_saves_list_size; ++l)
			{
				if( !m_saves_list_table[l].trainer_table ||
					!m_saves_list_table[l].trainer_table->Test(proc_manager.GetProcess(i).szExeFile) ) continue;

				if(choice.GetSelection() == process_num)
				{
					process_num = i;
					finded = true;
				}
				else
				{
					process_num++;
				}

				break;
			}
		}

		m_path = proc_manager.GetProcess(process_num).szExeFile;

		ProcessTableGUI* reader = nullptr;

		for(u32 i=0; i<m_saves_list_size; ++i)
		{
			if( !m_saves_list_table[i].trainer_table ||
				!m_saves_list_table[i].trainer_table->Test(m_path) ) continue;

			reader = m_saves_list_table[i].trainer_table;
			SetLabel(wxGetApp().GetAppName() + " - " + m_saves_list_table[i].name);
			break;
		}

		if(!reader)
		{
			wxMessageBox("Unsupported game.");
			return 1;
		}

		if(m_reader) m_reader->Close();

		if(!reader->Open(m_path))
		{
			wxMessageBox("Opening error.");
			return 1;
		}

		m_reader = reader;
		return 0;
	}

	void OnRefresh(wxCommandEvent&)
	{
		if(m_reader) m_reader->Load();
	}

	void OnOk(wxCommandEvent&)
	{
		if(m_reader)
		{
			m_reader->Save();
			wxMessageBox("Saved.");
			return;
		}
		
		wxMessageBox("Saving failed.");
	}

	void OnResize(wxSizeEvent&)
	{
		p_main.SetSize(GetClientSize());
	}

	void OnSelectFile(wxCommandEvent&)
	{
		while(int result = SelectFile())
		{
			switch(result)
			{
			case 1: continue;
			case 2: return;
			}
		}

		BuildGuiFile();
	}

	void OnSelectProcess(wxCommandEvent&)
	{
		while(int result = SelectProcess())
		{
			switch(result)
			{
			case 1: continue;
			case 2: return;
			}
		}

		BuildGuiProcess();
	}

	void OnExit(wxCommandEvent&)
	{
		Close();
	}
};

bool popmftApp::OnInit()
{
	SetAppName("popmft");

	//(new TrainerFrame(NULL))->Show();
	(new MainFrame())->Show();

	return true;
}

void popmftApp::Exit()
{
	wxApp::Exit();
}
